package com.example.untils;

import java.text.DecimalFormat;

import org.springframework.util.StringUtils;

/**
 * 
 * <b>百度地图相关工具类</b><br>
 * 1、包含坐标转换<br>
 * 2、百度地图计算两点间距离 WGS-84：是国际标准，GPS坐标（Google Earth使用、或者GPS模块）
 * GCJ-02：中国坐标偏移标准，Google Map、高德、腾讯使用 BD-09：百度坐标偏移标准，Baidu Map使用
 * 
 * @author lli
 * @version [2016年8月15日]
 */
public class BaiduMapUtils {

    public static final String BAIDU_LBS_TYPE = "bd09ll";
    public static final String CODES = "0123456789abcdefghijkmnpqrstuvwxyz";

    public static final double X_PI = 3.1415926535897932384626;
    public static final double PI = 3.14159265358979324 * 3000.0 / 180.0;
    public static final double A = 6378245.0;
    public static final double EE = 0.00669342162296594323;

    /**
     * wgs84转GCj02
     * 
     * @param lat
     * @param lon
     * @return
     */
    public static Gps gps84_To_Gcj02(double lat, double lon) {
        if (outOfChina(lat, lon)) {
            return null;
        }
        double dLat = transformLat(lon - 105.0, lat - 35.0);
        double dLon = transformLon(lon - 105.0, lat - 35.0);
        double radLat = lat / 180.0 * X_PI;
        double magic = Math.sin(radLat);
        magic = 1 - EE * magic * magic;
        double sqrtMagic = Math.sqrt(magic);
        dLat = (dLat * 180.0) / ((A * (1 - EE)) / (magic * sqrtMagic) * X_PI);
        dLon = (dLon * 180.0) / (A / sqrtMagic * Math.cos(radLat) * X_PI);
        double mgLat = lat + dLat;
        double mgLon = lon + dLon;
        return new Gps(mgLat, mgLon);
    }

    /**
     * GCj02转wgs84
     * 
     * @param lat
     * @param lon
     * @return
     */
    public Gps gcj_To_Gps84(double lat, double lon) {
        Gps gps = transform(lat, lon);
        double lontitude = lon * 2 - gps.getLng();
        double latitude = lat * 2 - gps.getLat();
        return new Gps(latitude, lontitude);
    }

    /**
     * GCj02转百度地图坐标
     * 
     * @param lat
     * @param lon
     * @return
     */

    public static Gps gcj02_To_Bd09(double gg_lat, double gg_lon) {
        double x = gg_lon, y = gg_lat;
        double z = Math.sqrt(x * x + y * y) + 0.00002 * Math.sin(y * PI);
        double theta = Math.atan2(y, x) + 0.000003 * Math.cos(x * PI);
        double bd_lon = z * Math.cos(theta) + 0.0065;
        double bd_lat = z * Math.sin(theta) + 0.006;
        return new Gps(bd_lat, bd_lon);
    }

    /**
     * 百度地图坐标转GCj02
     * 
     * @param lat
     * @param lon
     * @return
     */

    public Gps bd09_To_Gcj02(double bd_lat, double bd_lon) {
        double x = bd_lon - 0.0065, y = bd_lat - 0.006;
        double z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * PI);
        double theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * PI);
        double gg_lon = z * Math.cos(theta);
        double gg_lat = z * Math.sin(theta);
        return new Gps(gg_lat, gg_lon);
    }

    /**
     * 百度坐标转WGs84 (百度坐标转原始坐标)
     * 
     * @param bd_lat
     * @param bd_lon
     * @return
     */

    public Gps bd09_To_Gps84(double bd_lat, double bd_lon) {
        Gps gcj02 = bd09_To_Gcj02(bd_lat, bd_lon);
        Gps map84 = gcj_To_Gps84(gcj02.getLat(), gcj02.getLng());
        return map84;
    }

    /**
     * WGs84转百度坐标 (原始坐标转百度坐标)
     * 
     * @param lat
     * @param lon
     * @return
     */

    public static Gps Gps84_To_bd09(double lat, double lon) {
        Gps gcj02 = gps84_To_Gcj02(lat, lon);
        Gps bd09 = gcj02_To_Bd09(gcj02.getLat(), gcj02.getLng());
        return bd09;
    }

    /**
     * 凯立德转百度坐标
     */

    public Gps kld_To_bd09(double kld_lat, double kld_lon) {
        String k = kld_To_K(Double.valueOf(kld_lon), Double.valueOf(kld_lat));
        Gps kGJC = k_To_Gcj02(k);
        Gps result = gcj02_To_Bd09(kGJC.getLat(), kGJC.getLng());
        return result;
    }

    /**
     * 百度地图计算两点间距离
     * 
     * @param a
     * @param b
     * @return
     */
    public double getBaiduDistance(Gps a, Gps b) {
        double c = wv(a, b);
        return c;
    }

    /**
     * K码转Gcj02经度
     * 
     * @param lat
     * @param lon
     * @return
     */
    public static Gps k_To_Gcj02(String k) {
        double lat = __decode(k.substring(5, 9));
        if (k.charAt(0) <= '6') {
            lat += 35000000;
        }
        lat += 5000000;

        double lon = __decode(k.substring(1, 5));
        if (k.charAt(0) == '5' || k.charAt(0) == '8') {
            lon += 35000000;
        }
        lon += 70000000;

        return new Gps(lat / 1000000.0, lon / 1000000.0);
    }

    /**
     * GCJ02转成K码
     * 
     * @param gg_lat
     * @param gg_lon
     * @return
     */
    public String gcj02_To_K(double gg_lat, double gg_lon) {
        int lat = Integer.parseInt(new DecimalFormat("0").format((gg_lat)));
        int lon = Integer.parseInt(new DecimalFormat("0").format((gg_lon)));
        String k;
        lon -= 70000000;
        lat -= 5000000;
        if (lat > 35000000) {
            if (lon <= 35000000) {
                k = "6";
            } else {
                k = "5";
            }
        } else {
            if (lon <= 35000000) {
                k = "7";
            } else {
                k = "8";
            }
        }
        if (lon > 35000000) {
            lon -= 35000000;
        }
        if (lat > 35000000) {
            lat -= 35000000;
        }
        k += __encode(lon);
        k += __encode(lat);
        return k;
    }

    /**
     * 凯立德坐标转成K码
     * 
     * @param kld_lat
     * @param kld_lon
     * @return
     */
    public String kld_To_K(double kld_lat, double kld_lon) {
        String k = "";
        double a = kld_lat;
        double b = kld_lon;
        char[] c = new char[10];
        double d, e, f, h, i;
        a = Math.round(a / 100 - 3337);
        b = Math.round(b / 100 - 2373);
        if (a < 2520000 || a > 5040000 || b < 180000 || b > 2700000) {
            return "100000000";
        }
        d = a - 2520000;
        e = b - 180000;
        if (d > 1260000) {
            d -= 1260000;
            if (e > 1260000) {
                e -= 1260000;
                f = 1;
            } else {
                f = 4;
            }
        } else if (e > 1260000) {
            e -= 1260000;
            f = 2;
        } else {
            f = 3;
        }
        c[0] = (char) ((int) ("4".charAt(0)) + f);
        f = d;
        d = 1;
        for (i = 0; f > 0 && i < 4;) {
            h = f % 34;
            c[(int) d] = h < 10 ? (char) ((int) ("0".charAt(0)) + h) : h < 21 ? (char) ((int) ("A".charAt(0)) + h - 10)
                    : h < 23 ? (char) ((int) ("M".charAt(0)) + h - 21) : (char) ((int) ("P".charAt(0)) + h - 23);
            d++;
            i++;
            f /= 34;
        }
        for (f = i; f < 4; f++) {
            c[(int) d] = "0".charAt(0);
            d++;
        }
        f = e;
        for (i = 0; f > 0 && i < 4;) {
            h = f % 34;
            c[(int) d] = h < 10 ? (char) ((int) ("0".charAt(0)) + h) : h < 21 ? (char) ((int) ("A".charAt(0)) + h - 10)
                    : h < 23 ? (char) ("M".charAt(0) + h - 21) : (char) ((int) ("P".charAt(0)) + h - 23);
            d++;
            i++;
            f /= 34;
        }
        for (f = i; f < 4; f++) {
            c[(int) d] = "0".charAt(0);
            d++;
        }
        c[(int) d] = 0;
        for (char str : c) {
            k += str;
        }
        return k.substring(0, 9).toLowerCase();
    }

    private static double wv(Gps a, Gps b) {
        if (StringUtils.isEmpty(a.getLat()) || StringUtils.isEmpty(a.getLng()) || StringUtils.isEmpty(b.getLat())
                || StringUtils.isEmpty(b.getLng())) {
            return 0;
        }
        double alat = a.getLat(), alon = a.getLng(), blat = b.getLat(), blon = b.getLng();
        alon = ew(alon, -180, 180);
        alat = lw(alat, -74, 74);
        blon = ew(blon, -180, 180);
        blat = lw(blat, -74, 74);
        return Td(oi(alon), oi(blon), oi(alat), oi(blat));
    }

    private static double lw(double a, int b, int c) {
        a = a > b ? a : b;
        a = a > c ? c : a;
        return a;
    }

    private static double ew(double a, int b, int c) {
        if (a > c) {
            a -= c - b;
        }
        if (a < b) {
            a += c - b;
        }
        return a;
    }

    private static double oi(double a) {
        return Math.PI * a / 180;
    }

    private static double Td(double a, double b, double c, double d) {
        return 6370996.81 * Math.acos(Math.sin(c) * Math.sin(d) + Math.cos(c) * Math.cos(d) * Math.cos(b - a));
    }

    private static String __encode(int v) {
        String pch = "";
        v = v * 9 / 250;
        for (int i = 0; i < 4; ++i) {
            pch += CODES.charAt(v % 34);
            v /= 34;
        }
        return pch;
    }

    private static Gps transform(double lat, double lon) {
        if (outOfChina(lat, lon)) {
            return new Gps(lat, lon);
        }
        double dLat = transformLat(lon - 105.0, lat - 35.0);
        double dLon = transformLon(lon - 105.0, lat - 35.0);
        double radLat = lat / 180.0 * X_PI;
        double magic = Math.sin(radLat);
        magic = 1 - EE * magic * magic;
        double sqrtMagic = Math.sqrt(magic);
        dLat = (dLat * 180.0) / ((A * (1 - EE)) / (magic * sqrtMagic) * X_PI);
        dLon = (dLon * 180.0) / (A / sqrtMagic * Math.cos(radLat) * X_PI);
        double mgLat = lat + dLat;
        double mgLon = lon + dLon;
        return new Gps(mgLat, mgLon);
    }

    private static boolean outOfChina(double lat, double lon) {
        if (lon < 72.004 || lon > 137.8347)
            return true;
        if (lat < 0.8293 || lat > 55.8271)
            return true;
        return false;
    }

    private static double transformLat(double x, double y) {
        double ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x));
        ret += (20.0 * Math.sin(6.0 * x * X_PI) + 20.0 * Math.sin(2.0 * x * X_PI)) * 2.0 / 3.0;
        ret += (20.0 * Math.sin(y * X_PI) + 40.0 * Math.sin(y / 3.0 * X_PI)) * 2.0 / 3.0;
        ret += (160.0 * Math.sin(y / 12.0 * X_PI) + 320 * Math.sin(y * X_PI / 30.0)) * 2.0 / 3.0;
        return ret;
    }

    private static double transformLon(double x, double y) {
        double ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x));
        ret += (20.0 * Math.sin(6.0 * x * X_PI) + 20.0 * Math.sin(2.0 * x * X_PI)) * 2.0 / 3.0;
        ret += (20.0 * Math.sin(x * X_PI) + 40.0 * Math.sin(x / 3.0 * X_PI)) * 2.0 / 3.0;
        ret += (150.0 * Math.sin(x / 12.0 * X_PI) + 300.0 * Math.sin(x / 30.0 * X_PI)) * 2.0 / 3.0;
        return ret;
    }

    private static double __decode(String k) {
        double v = 0;
        for (int i = 3; i >= 0; --i) {
            v = v * 34 + (CODES.indexOf(k.charAt(i)));
        }
        v = v * 250 / 9;
        return v;
    }

    public static void main(String[] args) {

        // 北斗芯片获取的经纬度为WGS84地理坐标 31.426896,119.496145
        Gps gps = new Gps(29.575429778924, 114.21892734521);
        // 跟百度api接口有很小的误差
        Gps gcj = Gps84_To_bd09(gps.getLat(), gps.getLng());
        System.out.println("bd09 :" + gcj);
    }
}
